/************************************************************************
* env.c
* Copyright (C) Lisa Milne 2013 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "dish.h"
#include "env.h"
#include "path.h"

char* posix_env[] = {
	"ARFLAGS",
	"IFS",
	"MAILPATH",
	"PS1",
	"CC",
	"LANG",
	"MAILRC",
	"PS2",
	"CDPATH",
	"LC_ALL",
	"MAKEFLAGS",
	"PS3",
	"CFLAGS",
	"LC_COLLATE",
	"MAKESHELL",
	"PS4",
	"CHARSET",
	"LC_CTYPE",
	"MANPATH",
	"PWD",
	"COLUMNS",
	"LC_MESSAGES",
	"MBOX",
	"RANDOM",
	"DATEMSK",
	"LC_MONETARY",
	"MORE",
	"SECONDS",
	"DEAD",
	"LC_NUMERIC",
	"MSGVERB",
	"SHELL",
	"EDITOR",
	"LC_TIME",
	"NLSPATH",
	"TERM",
	"ENV",
	"LDFLAGS",
	"NPROC",
	"TERMCAP",
	"EXINIT",
	"LEX",
	"OLDPWD",
	"TERMINFO",
	"FC",
	"LFLAGS",
	"OPTARG",
	"TMPDIR",
	"FCEDIT",
	"LINENO",
	"OPTERR",
	"TZ",
	"FFLAGS",
	"LINES",
	"OPTIND",
	"USER",
	"GET",
	"LISTER",
	"PAGER",
	"VISUAL",
	"GFLAGS",
	"LOGNAME",
	"PATH",
	"YACC",
	"HISTFILE",
	"LPDEST",
	"PPID",
	"YFLAGS",
	"HISTORY",
	"MAIL",
	"PRINTER",
	"HISTSIZE",
	"MAILCHECK",
	"PROCLANG",
	"HOME",
	"MAILER",
	"PROJECTDIR",
	NULL
};

typedef struct env_s {
	struct env_s *next;
	char* name;
	unsigned int h_name;
	char* value;
} env_t;

typedef struct scope_s {
	struct scope_s *prev;
	struct scope_s *next;
	env_t *env;
} scope_t;

static scope_t *scopes = NULL;
static scope_t *current_scope = NULL;

static env_t *env_find_h(unsigned int h, char* name)
{
	scope_t *s = current_scope;
	env_t *e;
	while (s) {
		e = s->env;
		while (e) {
			if (e->h_name == h && !strcmp(e->name,name))
				return e;
			e = e->next;
		}
		s = s->prev;
	}
	return NULL;
}

static env_t *env_find(char* name)
{
	unsigned int h = hash(name);
	return env_find_h(h,name);
}

/* load environment variables from file */
int env_fromfile(char* file)
{
	return 0;
}

/* reload the environment */
void env_reload()
{
	int i;
	char *v;
	env_push();
	for (i=0; posix_env[i]; i++) {
		v = getenv(posix_env[i]);
		if (v)
			env_set(posix_env[i],v,1);
	}

	if (!env_find("HOSTNAME")) {
		char buff[255];
		if (!gethostname(buff,255))
			env_set("HOSTNAME",buff,1);
	}

	if (!env_find("nick")) {
		v = env_get("USER");
		if (!v)
			v = get_username();
		env_set("nick",v,1);
	}

	v = env_get("PATH");
	if (!v) {
		ERROR("No PATH found, using defaults\n");
		env_set("PATH",DEFAULT_PATH,1);
	}

	epath_update();
}

/* clear the current environment */
void env_clear()
{
	scope_t *s;
	env_t *e;
	while (scopes) {
		s = scopes;
		scopes = scopes->next;
		if (scopes)
			scopes->prev = NULL;
		while (s->env) {
			e = s->env;
			s->env = e->next;
			if (e->name)
				free(e->name);
			if (e->value)
				free(e->value);
			free(e);
		}
		free(s);
	}
	current_scope = NULL;
}

/* refresh the current environment */
void env_refresh()
{
	env_clear();
	env_reload();
}

/* set an environment variable */
int env_set(char* name, char* value, int global)
{
	unsigned int h;
	env_t *e;
	env_t **env;
	char* v = NULL;

	if (!value && (v = strchr(name,'='))) {
		*v = 0;
		value = v+1;
	}

	h = hash(name);
	e = env_find_h(h,name);

	if (e) {
		if (e->value)
			free(e->value);
		if (value) {
			e->value = strdup(value);
		}else{
			e->value = NULL;
		}
	}else if (value) {
		e = malloc(sizeof(env_t));
		e->name = strdup(name);
		e->h_name = h;
		e->value = strdup(value);
		e->next = NULL;
		if (global) {
			env = &scopes->env;
		}else{
			env = &current_scope->env;
		}
		if (*env) {
			env_t *l = *env;
			while (l->next) {
				l = l->next;
			}
			l->next = e;
		}else{
			*env = e;
		}
	}
	if (!strcmp(name,"PATH"))
		epath_update();

	if (v)
		*v = '=';

	return 0;
}

/* set an environment variable to an integer value */
int env_set_int(char* name, int value, int global)
{
	char buff[50];
	sprintf(buff,"%d",value);
	return env_set(name,buff,global);
}

/* get the value of an environment variable */
char* env_get(char* name)
{
	env_t *e = env_find(name);
	if (e)
		return e->value;

	return NULL;
}

/* creates an array from the env */
char** env_toclist()
{
	char** env;
	int i;
	scope_t *s = scopes;
	env_t *e;
	char buff[1024];

	env = clist_gen(NULL);

	while (s) {
		e = s->env;
		while (e) {
			sprintf(buff,"%s=",e->name);
			i = clist_findn(env,buff);
			strcat(buff,e->value);
			if (i > -1) {
				env = clist_replacev(env,i,buff);
			}else{
				env = clist_append(env,buff,NULL);
			}
			e = e->next;
		}
		s = s->next;
	}

	return env;
}

/* push a new scope onto the env stack */
void env_push()
{
	scope_t *s = malloc(sizeof(scope_t));

	s->next = NULL;
	s->prev = current_scope;

	if (current_scope)
		current_scope->next = s;

	current_scope = s;

	if (!scopes)
		scopes = s;

	s->env = NULL;
}

/* pop the top scope of the env stack */
void env_pop()
{
	scope_t *s = current_scope;
	env_t *e;
	if (!s || !s->prev)
		return;

	current_scope = s->prev;
	current_scope->next = NULL;

	while (s->env) {
		e = s->env;
		s->env = e->next;
		if (e->name)
			free(e->name);
		if (e->value)
			free(e->value);
		free(e);
	}
	free(s);
}
